﻿#region License Info
//Component of Cronos Package, http://www.codeplex.com/cronos
//Copyright (C) 2009 Anthony Brockwell

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#endregion


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using MathNet.Numerics.LinearAlgebra;
using PdfSharp.Drawing;

namespace ExtraControlLibrary
{
    public partial class BarPlotBox : PartitionedBlankGraph
    {
        protected Matrix bars;

        protected Vector mins;
        protected Vector maxes;
        protected double xunits;
        protected double yunits;

        protected double topY, bottomY, leftX, rightX;

        public BarPlotBox()
        {
            InitializeComponent();
            TitleVerticalProportion = 0.10;
        }

        protected void ScanForLimits()
        {
            mins = new Vector(3);
            maxes = new Vector(3);

            // anchor the mins and maxes at 0 so that zero is always included in the y-axis
            for (int j = 0; j < 3; ++j)
            {
                mins[j] = 0;
                maxes[j] = 0;
            }

            for (int t = 0; t < bars.RowCount; ++t)
                for (int j = 0; j < 3; ++j)
                {
                    if (bars[t, j] > maxes[j])
                        maxes[j] = bars[t, j];
                    if (bars[t, j] < mins[j])
                        mins[j] = bars[t, j];
                }

            // and then get units
            xunits = GetNiceUnitForRange(mins[0], maxes[0], 1.0, false);
            yunits = GetNiceUnitForRange(mins[1], maxes[1], 1.0, false);

            leftX = Math.Floor(mins[0] / xunits) * xunits;
            rightX = Math.Ceiling(maxes[0] / xunits) * xunits;

            bottomY = Math.Floor(mins[1] / yunits) * yunits;
            topY = Math.Ceiling(maxes[1] / yunits) * yunits;
        }

        public virtual void SetBars(Matrix bars)
        {
            this.bars = bars;
            ScanForLimits();
        }

        protected double LabelMinYValue()
        {
            return bottomY;            
        }

        protected double LabelMaxYValue()
        {
            return topY;
        }

        protected double LabelMinXValue()
        {
            return leftX;
        }

        protected double LabelMaxXValue()
        {
            return rightX;
        }

        protected override double MinYValue()
        {
            return bottomY - yunits;
        }

        protected override double MaxYValue()
        {
            return topY + yunits;
        }

        protected override double LeftXValue()
        {
            return leftX - xunits;
        }

        protected override double RightXValue()
        {
            return rightX + xunits;
        }

        protected virtual void AfterLabelsBeforeBars(object graphicsObject)
        {
        }

        public void RenderToXGraphics(XGraphics g, int titleFontSize, int labelFontSize)
        {
            using (var title_font = new Font("Arial", titleFontSize, FontStyle.Bold, GraphicsUnit.World))
            using (var label_font = new Font("Arial", labelFontSize, GraphicsUnit.World))
            {
                // title if not null
                if (Title != null)
                    DrawTitle(title_font, g);
                DrawGraphFrame(g);

                // now if nothing to plot, return
                if (bars == null) return;
                if (bars.RowCount < 1) return;

                // x-labels
                var xvals = new List<double>(100);
                var xvalstrings = new List<string>(100);
                for (double tx = LabelMinXValue(); tx <= LabelMaxXValue(); tx += xunits)
                {
                    xvals.Add(tx);
                    xvalstrings.Add(string.Format("{0}", tx));
                }
                DrawAllXLabels(g, label_font, xvals, xvalstrings, null);
                foreach (double xv in xvals)
                {
                    int pixX = MaptoXPixel(xv);
                    g.DrawLine(Pens.LightGray, pixX, GraphRectangle.Top + 1, pixX, GraphRectangle.Bottom - 1);
                }

                // y-labels
                var yvals = new List<double>(100);
                var yvalstrings = new List<string>(100);
                for (double ty = LabelMinYValue(); ty <= LabelMaxYValue(); ty += yunits)
                {
                    yvals.Add(ty);
                    yvalstrings.Add(string.Format("{0:0.00}", ty));
                }
                DrawAllYLabels(g, label_font, yvals, yvalstrings, null, MinYValue(), MaxYValue());
                foreach (double yv in yvals)
                {
                    int pixY = MaptoYPixel(yv);
                    g.DrawLine(Pens.LightGray, GraphRectangle.Left + 1, pixY, GraphRectangle.Right - 1, pixY);
                }

                // then anything intermediate
                AfterLabelsBeforeBars(g);

                // then bars
                for (int t = 0; t < bars.RowCount; ++t)
                {
                    float xpixel0 = MaptoXPosition(bars[t, 0] - bars[t, 2] / 2.0);
                    float xpixel1 = MaptoXPosition(bars[t, 0] + bars[t, 2] / 2.0);
                    float ypixel1 = MaptoYPosition(0);
                    float ypixel0 = MaptoYPosition(bars[t, 1]);
                    XRect rect;
                    if (ypixel1 > ypixel0)
                        rect = new XRect(xpixel0, ypixel0, (xpixel1 - xpixel0 + 1), (ypixel1 - ypixel0));
                    else
                        rect = new XRect(xpixel0, ypixel1, (xpixel1 - xpixel0 + 1), (ypixel0 - ypixel1));

                    // g.FillRectangle(Brushes.LightBlue, rect);
                    g.DrawRectangle(Pens.Black, rect);
                }

                // then draw the zero-line
                int x0 = GraphRectangle.Left;
                int x1 = GraphRectangle.Right;
                int y0 = MaptoYPixel(0);
                g.DrawLine(Pens.Gray, x0, y0, x1, y0);
            }                        
        }

        public void RenderToGraphics(Graphics g, int titleFontSize, int labelFontSize)
        {
            using (var title_font = new Font("Arial", titleFontSize, FontStyle.Bold))
            using (var label_font = new Font("Arial", labelFontSize))
            {
                // title if not null
                if (Title != null)
                    DrawTitle(title_font, g);
                ClearGraphArea(g);
                DrawGraphFrame(g);

                // now if nothing to plot, return
                if (bars == null) return;
                if (bars.RowCount < 1) return;

                // x-labels
                var xvals = new List<double>(100);
                var xvalstrings = new List<string>(100);
                for (double tx = LabelMinXValue(); tx <= LabelMaxXValue(); tx += xunits)
                {
                    xvals.Add(tx);
                    xvalstrings.Add(string.Format("{0}", tx));
                }
                DrawAllXLabels(g, label_font, xvals, xvalstrings, null);
                foreach (double xv in xvals)
                {
                    int pixX = MaptoXPixel(xv);
                    g.DrawLine(Pens.LightGray, pixX, GraphRectangle.Top + 1, pixX, GraphRectangle.Bottom - 1);
                }

                // y-labels
                var yvals = new List<double>(100);
                var yvalstrings = new List<string>(100);
                for (double ty = LabelMinYValue(); ty <= LabelMaxYValue(); ty += yunits)
                {
                    yvals.Add(ty);
                    yvalstrings.Add(string.Format("{0:0.00}", ty));
                }
                DrawAllYLabels(g, label_font, yvals, yvalstrings, null, MinYValue(), MaxYValue());
                foreach (double yv in yvals)
                {
                    int pixY = MaptoYPixel(yv);
                    g.DrawLine(Pens.LightGray, GraphRectangle.Left + 1, pixY, GraphRectangle.Right - 1, pixY);
                }

                // then anything intermediate
                AfterLabelsBeforeBars(g);

                // then bars
                for (int t = 0; t < bars.RowCount; ++t)
                {
                    int xpixel0 = MaptoXPixel(bars[t, 0] - bars[t, 2] / 2.0);
                    int xpixel1 = MaptoXPixel(bars[t, 0] + bars[t, 2] / 2.0);
                    int ypixel1 = MaptoYPixel(0);
                    int ypixel0 = MaptoYPixel(bars[t, 1]);
                    Rectangle rect;
                    if (ypixel1 > ypixel0)
                        rect = new Rectangle(xpixel0, ypixel0, (xpixel1 - xpixel0 + 1), (ypixel1 - ypixel0));
                    else
                        rect = new Rectangle(xpixel0, ypixel1, (xpixel1 - xpixel0 + 1), (ypixel0 - ypixel1));

                    g.FillRectangle(Brushes.LightBlue, rect);
                    g.DrawRectangle(Pens.Black, rect);
                }

                // then draw the zero-line
                int x0 = GraphRectangle.Left;
                int x1 = GraphRectangle.Right;
                int y0 = MaptoYPixel(0);
                g.DrawLine(Pens.Gray, x0, y0, x1, y0);
            }            
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            Graphics g = pe.Graphics;

            // Preliminary processing: features of data we need to plot have already been set by ScanData
            // ScanData is called by set method for XYMatrix.

            // Now we partition into sub-boxes of this box for title, x-labels, y-labels, and graph
            CreatePartition(new Rectangle(0,0,Size.Width, Size.Height));

            // work out font sizes
            int titleHeight = (int) Math.Round(TitleRectangle.Height/3.0);
            int labelHeight = (int)Math.Round(GraphRectangle.Height /12.0);
            int labelWidth = (int)Math.Round(GraphRectangle.Height / 30.0);
            int labelSize = Math.Min(labelHeight, labelWidth);

            RenderToGraphics(g, titleHeight, labelSize);
        }
    }
}
